#ifndef UTILS_STATIC_IO_NOTIFIER_H_INCLUDED
#define UTILS_STATIC_IO_NOTIFIER_H_INCLUDED

#include <utils/basic_io_notifier.h>

namespace utils
{

//DerivedT for CRTP, ContextT - provides a context for current fd, N - maximum size of fds may be notified
template <typename DerivedT, typename ContextT, size_t N>
class static_io_notifier
    : private basic_io_notifier
{
public:
    enum
    {
        static_count = N
    };

    enum notify_events
    {
        NOTIFY_IN  = EPOLLIN
      , NOTIFY_OUT = EPOLLOUT
    };

    enum notify_status
    {
        NOTIFY_SUCCESS = 0
      , NOTIFY_TIMEOUT
      , NOTIFY_ERROR
      , NOTIFY_INTERRUPT
    };

    static_io_notifier()
        : basic_io_notifier()
    { }
    ~static_io_notifier()
    { }

    using basic_io_notifier::del_event;

    template <uint32_t Event>
    bool add_event(int fd, ContextT * data = 0)
    {
        return basic_io_notifier::add_event<Event>(fd, data);
    }
    template <uint32_t Event>
    bool mod_event(int fd, ContextT * data = 0)
    {
        return basic_io_notifier::mod_event<Event>(fd, data);
    }

    notify_status wait(int const timeout = -1)
    {
        int const cnt = basic_io_notifier::wait(events_, N, timeout);
        if(cnt < 0)
        {
            throw utils::system_exception(errno);
        }
        if(cnt == 0)
        {
            return NOTIFY_TIMEOUT;
        }
        notify_status status = NOTIFY_SUCCESS;

        size_t nFd = 0;
        do
        {
            DerivedT * self = static_cast<DerivedT *>(this);
            ContextT * ctx  = static_cast<ContextT *>(events_[nFd].data.ptr);

            uint32_t const eventSet = events_[nFd].events;

            union
            {
                uint32_t value;
                uint8_t  bytes[sizeof(uint32_t)];

                uint8_t & operator[](size_t idx)
                {
                    return bytes[idx];
                }

            } fset = {};

            if(eventSet & (EPOLLERR))
            {
                fset[0] = self->on_error(ctx, errno);
            }
            else
            {
                fset[0] = (eventSet & (EPOLLOUT)) && !self->on_data_out(ctx);
                fset[1] = (eventSet & (EPOLLIN)) && !self->on_data_in(ctx);
                fset[2] = (eventSet & (EPOLLRDHUP | EPOLLHUP)) && !self->on_hangup(ctx);
            }
            if(fset.value != 0)
            {
                status = NOTIFY_INTERRUPT;
            }
        }
        while(++nFd < size_t(cnt));

        return status;
    }

private:
    ::epoll_event events_[N];
};

}

#endif // UTILS_STATIC_IO_NOTIFIER_H_INCLUDED
